﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.IO;
using Vbyte.Extension;
using Vbyte.DataSource.Data;

namespace Vbyte.DataSource.Cache
{
    /// <summary>
    /// 基于系统内存的数据缓存
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    public class MemoryCachedPolicy<T> : ICachePolicy<T, PropertyBindIdentity>
       where T : class, ICacheTimeSupport,IObjectIdentification<PropertyBindIdentity>, new()
    {
        /// <summary>
        /// 新建基于系统内存的数据缓存实例
        /// </summary>
        public MemoryCachedPolicy()
        {
            if (!MemoryCacheHelper.MemoryDbLoad)
            {
                MemoryCacheHelper.LoadDb();
            }

        }
        
        private T instance = new T();
        private Type instanceType = typeof(T);

        #region ICachePolicy<T,PropertyBindIdentity> 成员

        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        public void AddCache(PropertyBindIdentity identity)
        {
            instance = DataExchangeHelper.Load<T>(identity);
            if (instance != null)
            {
                AddToCache(GetStringID(identity), instance);
            }
        }

        private void ApplyChanges()
        {
            MemoryCacheHelper.MemoryDb.AcceptChanges();
        }

        private string GetStringID(PropertyBindIdentity identity)
        {
            return DataExchangeHelper.MD5(identity.GetBytes(), true);
        }

        private void AddToCache(string id, T instance)
        {
            if (MemoryCacheHelper.MemoryDb.Tables[instanceType.FullName] == null) CreatTable();
            DataTable dTab = MemoryCacheHelper.MemoryDb.Tables[instanceType.FullName];
            DataRow dRow = dTab.NewRow();
            dRow["id"] = id;
            dRow["dat"] = instance;

            dTab.Rows.Add(dRow);
            dTab.AcceptChanges();
            ApplyChanges();
        }

        private void CreatTable()
        {
            DataTable dTab = new DataTable(instanceType.FullName);
            DataColumn keyCol = new DataColumn("id", typeof(string));
            dTab.Columns.Add(keyCol);
            dTab.Columns.Add(new DataColumn("dat", instanceType));
            dTab.PrimaryKey = new DataColumn[] { keyCol };
            MemoryCacheHelper.MemoryDb.Tables.Add(dTab);
            ApplyChanges();
        }

        private bool Exists(PropertyBindIdentity id)
        {
            return Exists(GetStringID(id), ref instance);
        }

        private bool Exists(string id,ref T tInstance)
        {
            bool rt = false;
            DataTable dTab = MemoryCacheHelper.MemoryDb.Tables[instanceType.FullName];
            if (dTab != null)
            {
                DataRow[] rowResult =  dTab.Select("id='" + id + "'");
                if (rowResult != null && rowResult.Length > 0)
                {
                    rt = true;

                    tInstance = (T)rowResult[0]["dat"];
                }
            }
            return rt; 
        }

        /// <summary>
        /// 加载数据，如果不使用缓存则更新上次缓存时间。
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        /// <param name="useCache">指示是否从缓存中加载</param>
        /// <returns>从数据源中获取的相关实例</returns>
        public T Load(PropertyBindIdentity identity, bool useCache)
        {
            T targetInstance = new T();
            string strID = GetStringID(identity);
            bool cacheExits = Exists(strID, ref targetInstance);
            if (useCache && cacheExits)
            {
                //Console.WriteLine("ID:{0}, 更新时间：{1}", strID, targetInstance.GetUpdateTimeUTC().ToLocalTime());
                return targetInstance;
            }

            bool blnSaveItem = false;
            if (!cacheExits) targetInstance = DataExchangeHelper.Load<T>(identity);
            targetInstance.SetIdentity(identity);
            targetInstance.UpdateCacheTimeUTC(DateTime.Now.ToUniversalTime());
            if (instanceType.GetInterface(typeof(ICacheValidatable).FullName) != null)
            {
                ICacheValidatable validateInstance = targetInstance as ICacheValidatable;
                if (validateInstance.IsValidate())
                {
                    blnSaveItem = true;
                }
            }
            else
            {
                blnSaveItem = true;
            }
            if (blnSaveItem)  AddToCache(strID, targetInstance);
            return targetInstance;
        }

        /// <summary>
        /// 刷新缓存
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        public void RefreshCache(PropertyBindIdentity identity)
        {
            instance = DataExchangeHelper.Load<T>(identity);
            string strID = GetStringID(identity);
            if (instance != null)
            {
                //Console.WriteLine("ThreadID:{0}, {1}",
                //    System.Threading.Thread.CurrentThread.ManagedThreadId,
                //    MemoryCacheHelper.MemoryDb != null
                //    );

                instance.UpdateCacheTimeUTC(DateTime.Now.ToUniversalTime()); 
                DataTable dTab = MemoryCacheHelper.MemoryDb.Tables[instanceType.FullName];
                if (dTab != null)
                {
                    DataRow[] rowResult = dTab.Select("id='" + strID + "'");
                    if (rowResult != null && rowResult.Length > 0)
                    {
                        dTab.Rows.Remove(rowResult[0]);

                        //Console.WriteLine("ID:{0}, 刷新时间：{1}", strID, instance.GetUpdateTimeUTC().ToLocalTime());

                        DataRow dRow = dTab.NewRow();
                        dRow["id"] = strID;
                        dRow["dat"] = instance;
                        dTab.Rows.Add(dRow);
                        dTab.AcceptChanges();
                        ApplyChanges();
                    }
                }
                else
                {
                    //Console.WriteLine("ID:{0}, 添加时间：{1}", strID, instance.GetUpdateTimeUTC().ToLocalTime()); 
                    AddToCache(strID, instance);
                }

            }
        }

        /// <summary>
        /// 清空缓存
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        public void RemoveCache(PropertyBindIdentity identity)
        {
            string strID = GetStringID(identity);
            DataTable dTab = MemoryCacheHelper.MemoryDb.Tables[instanceType.FullName];
            if (dTab != null)
            {
                DataRow[] rowResult = dTab.Select("id='" + strID + "'");
                if (rowResult != null && rowResult.Length > 0)
                {
                    dTab.Rows.Remove(rowResult[0]);
                    dTab.AcceptChanges();
                    ApplyChanges();
                }
            }
        }

        private CacheCompareMode _mode = CacheCompareMode.LocalCacheTime;
        /// <summary>
        /// 获取或设置该缓存策略的缓存比较模式
        /// </summary>
        /// <value></value>
        public CacheCompareMode Mode
        {
            get { return _mode; }
            set { _mode = value; }
        }

        private double cacheMinutes = 10.00;

        /// <summary>
        /// 获取或设置该缓存策略的缓存分钟数
        /// </summary>
        /// <value></value>
        public double CacheMinutes
        {
            get { return cacheMinutes; }
            set { cacheMinutes = value; }
        }

        #endregion

    }

    /// <summary>
    /// 内存数据辅助
    /// </summary>
    public static class MemoryCacheHelper
    {
        /// <summary>
        /// 内容数据库
        /// </summary>
        internal static DataSet MemoryDb = new DataSet("CacheMemoryDb");

        /// <summary>
        /// 是否已从缓存文件加载
        /// </summary>
        internal static bool MemoryDbLoad = false;

        private static string GetMemoryDbFile()
        {
            return String.Concat(AppDomain.CurrentDomain.BaseDirectory, "\\cache\\MemoryDb.bin");
        }

        /// <summary>
        /// 从持久化文件加载数据库
        /// </summary>
        public static void LoadDb()
        {
            byte[] binData = new byte[0];
            string filePath = GetMemoryDbFile();
            if (File.Exists(filePath))
            {
                using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    int totalLen = (int)fs.Length;
                    byte[] fDat = new byte[totalLen];
                    fs.Read(fDat, 0, totalLen);
                    binData = fDat;
                    fs.Close();
                }
            }

            if (binData.Length > 0)
            {
                MemoryDb = binData.GetObject<DataSet>();
                MemoryDbLoad = true;

                AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_DomainUnload);
                //AppDomain.CurrentDomain.UnhandledException += (sender, e) => {
                //    DataExchangeHelper.ThrowException(typeof(MemoryCacheHelper),
                //        (Exception)e.ExceptionObject,
                //        "Error-Local-" + DateTime.Now.ToString("yyyy-MM-dd"), null);
                //};
                //AppDomain.CurrentDomain.DomainUnload += new EventHandler(CurrentDomain_DomainUnload);
            }
        }

        static void CurrentDomain_DomainUnload(object sender, EventArgs e)
        {
            PersistencyDb();
        }

        /// <summary>
        /// 把内存数据库持久化
        /// </summary>
        public static void PersistencyDb()
        {
            byte[] fileDat = MemoryDb.GetBytes();
            string filePath = GetMemoryDbFile();
            DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(filePath));
            if (!di.Exists) di.Create();
            using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                fs.Write(fileDat, 0, fileDat.Length);
                fs.Close();
            }
        }

    }

}
